package model;

import java.io.*;
import java.util.*;

public class TockGame implements Serializable, TockConstants{
	private static final long serialVersionUID = 0; // What the hell is this??
	
	private int numOfPlayers;
	private TockBoard board;
	private TockDeck deck;
	
	private TockUsers users;
	
	private boolean started;
	private boolean jacked;
	
	private String lastCard;
	
	public TockGame(int playrs) {
		numOfPlayers = playrs;
		board = new TockBoard();
		users = new TockUsers();
		deck = new TockDeck(numOfPlayers);
		jacked = false;
		lastCard = "";
	}
	
	public boolean started() {return started;}
	public int getNextPlayer() {return users.getNextPlayer();}
	public TockUsers getUsers() {return users;}
	
	public String getLastCard() {return lastCard;}
	public void setLastCard(String lastCard) {this.lastCard = lastCard;}
	
	public TockBoard getBoard() {return board;}
	public int getDeckRemaining() { return deck.getSize(); }
	
	public int getNumOfPlayers() { return numOfPlayers; }
	
	/**
	 * Returns the hand of the given player (String). This assumes that the order of
	 *  players is the same as the order of the hands in the deck object.
	 * @param sender The name of the player's hand we want
	 * @return ArrayList of the hand
	 */
	public ArrayList<String> getHand(String sender) {
		int userIndex = users.getPlayerIndex(sender);
		if (userIndex == -1) return new ArrayList<String>();
		else return deck.getHand(userIndex);
	}
	
	/**
	 * Function that makes a move for the game.
	 * @param sender Name of the player making the move
	 * @param aCard Card number from 1 to 55
	 * @param source Source location of the move
	 * @param destination Destination location of the move
	 * @param movePacketType One of the MOVE_PACKET_TYPE_* constants
	 * @param sevenMoves An ArrayList of source/destinations for a 7-split move
	 * @return Error code. Found in TockConstants.MOVE_ERROR_***
	 */
	public int makeMove(String sender, int aCard, Location source, Location destination, int movePacketType, ArrayList<Location> sevenMoves) {
		//----- Get the value of the card and player id
		int card = TockDeck.valueOfCard(aCard);
		int playerId = BOARD_LOCATIONS[numOfPlayers][users.getPlayerIndex(sender)];
		
		//----- Check the errors
		//--- Don't allow the move if we haven't started
		if (!started) return MOVE_ERROR_NOT_STARTED;
		//--- Only allow the next player to play
		if (users.getPlayerIndex(sender) != users.getNextPlayer()) return MOVE_ERROR_NOT_TURN;
		
		//--- Deal with it if it's a discard
		if (movePacketType == MOVE_PACKET_TYPE_DISCARD) {
			//--- We can assume that it's a valid discard -> checking is done client-side
			//-- Waste the card
			deck.useCard(users.getPlayerIndex(sender), aCard);
			
			nextTurn();
			
			//--- Reset jack and finish play
			jacked = false;
			playDone();
			return MOVE_ERROR_NONE;
		}
		
		//--- Deal with it if it's a 7-split
		if (movePacketType == MOVE_PACKET_TYPE_SEVEN_PARTIAL) {
			//----- Handle the Jack-missed turn case
			if (jacked) {
				//--- Waste the card
				deck.useCard(users.getPlayerIndex(sender), aCard);
				
				nextTurn();
				
				//--- Reset jack and finish play
				jacked = false;
				playDone();
				return MOVE_ERROR_NONE;
			}
			
			//--- Move the pieces
			while (sevenMoves.size() > 0) {
				Location tempSrc = sevenMoves.remove(0);
				Location tempDst = sevenMoves.remove(0);
				board.movePiece(tempSrc, tempDst, playerId, false);
			}
			
			//--- Use the card
			deck.useCard(users.getPlayerIndex(sender), aCard);
			
			nextTurn();
			
			//--- Handle the dealing of cards if needed
			playDone();
			
			return MOVE_ERROR_NONE;
			
		}
		
		//--- Make sure the mover own's the piece
		if (!board.rightPiece(playerId, source)) return MOVE_ERROR_NOT_PIECE;
		//--- Make sure it's a valid move
		ArrayList<Location> moves = getPossibleMoves(source, card, sender, 7);
		boolean confirmed_move = false;
		for (int i=0; i<moves.size(); i++) {
			if (destination.isEqual(moves.get(i))) confirmed_move = true;
		}
		if (!confirmed_move) return MOVE_ERROR_INVALID_MOVE;
		
		//----- Handle the Jack-missed turn case
		if (jacked) {
			//--- Waste the card
			deck.useCard(users.getPlayerIndex(sender), aCard);
			
			//--- If joker played, deal again and let player play
			if (card == 25) {
				deck.dealCard(users.getPlayerIndex(sender));
			} else nextTurn();
			
			//--- Reset jack and finish play
			jacked = false;
			playDone();
			return MOVE_ERROR_NONE;
		}
		
		//--- Move the piece
		board.movePiece(source, destination, playerId,
				((movePacketType == MOVE_PACKET_TYPE_TEN_SWITCH) ? true : false));
		
		//--- Use the card
		deck.useCard(users.getPlayerIndex(sender), aCard);
		
		//----- Special card dealings
		//--- Jack means missed turn
		if (card == 11) {
			jacked = true;
		}
		//--- If joker, redeal the card (otherwise next person's turn)
		if (card == 25) {			
			//--- Deal a new card
			deck.dealCard(users.getPlayerIndex(sender));
		}
		
		//--- Next turn unless joker
		if (card != 25) nextTurn();
		//--- Handle the dealing of cards if needed
		playDone();
		
		return MOVE_ERROR_NONE;
	}
	
	/**
	 * Method to handle the new dealing, new deck, etc.
	 *
	 */
	public void playDone() {
		if (deck.handDone()) {
			if (deck.deckDone()) {
				deck = new TockDeck(numOfPlayers);
				deck.deal();
				jacked = false;
				nextTurn();
			} else {
				deck.deal();
				jacked = false;
			}
		}
	}
	
	
	/**
	 * Method to decide if a player has a valid move or not.
	 * @param sender The player in question
	 * @return
	 */
	public boolean canMove(String sender) {
		int player_num = users.getPlayerIndex(sender);
		
		//----- Get the hand and colour of the player
		ArrayList<String> hand = deck.getHand(player_num);
		int colour = BOARD_LOCATIONS[numOfPlayers][player_num];
		
		//----- Check each of the cards
		for (int i=0; i<hand.size(); i++) {
			int tempCard = TockDeck.valueOfCard(Integer.parseInt(hand.get(i)));
			ArrayList<ArrayList<Location>> moves = new ArrayList<ArrayList<Location>>();
			//--- Check all of the marbles
			for (int j=0; j<4; j++) {
				moves.add(getPossibleMoves(board.getLocation(colour, j), tempCard, player_num, 7));
			}
			
			//--- Need to be sneaky now because a 7-split may bugger us up
			if (tempCard == 7) {
				int countForSeven = 0;
				for (int j=0; j<moves.size(); j++) {
					countForSeven += moves.get(j).size();
				}
				if (countForSeven >= 7) return true;
			} else {
				for (int j=0; j<moves.size(); j++) {
					if (moves.get(j).size() > 0) return true;
				}				
			}
		}
		
		return false;
	}
	
	/**
	 * Registers a new person to play the game if there are seats left.
	 * @param sender Name of the new user
	 */
	public void sitPlayer(String sender) {
		if (users.numOfPlayers() != numOfPlayers) {
			users.newPlayer(sender, BOARD_LOCATIONS[numOfPlayers][users.numOfPlayers()]);
			if (!started && (users.numOfPlayers() == numOfPlayers)) {
				deck = new TockDeck(numOfPlayers);
				deck.deal();
				jacked = false;
				started = true;
				nextTurn();
			}
			users.removeChatter(sender);
		} else {
			users.newChatter(sender);
		}
	}
	
	/**
	 * Handles a new user to the game. Automatically adds them to the chat
	 * @param sender refers to the user who just joined
	 */
	public void register(String sender) {
		users.newChatter(sender);
	}
	
	/**
	 * Unregister's a user when the bugger takes off
	 * @param sender Person taking off
	 */
	public void unregister(String sender) {
		users.removePlayer(sender);
		users.removeChatter(sender);
	}
	
	/**
	 * Lets a player stand up from the game (stop playing but still chat)
	 * @param sender refers to the user wanting to stop playing
	 */
	public void standPlayer(String sender) {
		users.removePlayer(sender);
		users.newChatter(sender);
	}
	
	public void setUsers(TockUsers users) {this.users = users;}
	
	/**
	 * Method to move to the next turn.
	 *
	 */
	public void nextTurn() {
		int index = users.getNextPlayer();
		index = (index + 1) % users.numOfPlayers();
		users.setNextPlayer(index);
	}  
	
	public void pauseGame() { started = false; }
	public void unpauseGame() { started = true; }
	
	/**
	 * Method to get valid moves of a given card
	 * @param source The source location
	 * @param card the card id (from 1-13, 25, -4)
	 * @param player ID of the player
	 * @return ArrayList of the destination locations
	 */
	public ArrayList<Location> getPossibleMoves(Location source, int card, int player, int sevenCount) {
		return getPossibleMoves(source, card, player, numOfPlayers, board, sevenCount);
	}
	
	/**
	 * Method to get valid moves of a given card
	 * @param source The source location
	 * @param card the card id (from 1-13, 25, -4)
	 * @param player ID of the player
	 * @param theNumOfPlayers Number of players in the game
	 * @param theBoard The current board in question
	 * @return ArrayList of the destination locations
	 */
	public static ArrayList<Location> getPossibleMoves(Location source, int card, int player,
			int theNumOfPlayers, TockBoard theBoard, int sevenCount) {
		if (DEBUG_MOVE_GENERATION) {
			System.out.println("Generating moves for player " + player + " with card " + card + " from source:\n" + source);
		}
		
		ArrayList<Location> locations = new ArrayList<Location>();
		int colour = BOARD_LOCATIONS[theNumOfPlayers][player];
		
		//----- Check if we're trying to come out
		if (source.type == LOC_TYPE_START) {
			if (TockDeck.isComeOutCard(card)) {
				locations.add(new Location(LOC_TYPE_NORMAL, COME_OUT_SPOTS[colour]));
			} else {
				if (DEBUG_MOVE_GENERATION) System.out.println("No Move: Doesn't have come out card.");
			}
		}		
		
		//----- Check if we're home
		else if (source.type == LOC_TYPE_HOME) {
			//--- Do the 7 nastiness
			if (card == 7) {
				for (int i=source.loc+1; i<4; i++) {
					Location tempLoc = new Location(LOC_TYPE_HOME, colour, i);
					if ((theBoard.pieceAt(tempLoc))[0] != -1) return locations;
					if ((i-source.loc) <= sevenCount) locations.add(tempLoc);
				}
				return locations;
			}
			
			//--- Check if the card is too big
			if ((source.loc + Math.abs(card)) > 3) return locations;
			
			//--- Check it's path
			for (int i=1; i <= card; i++) {
				if ((theBoard.pieceAt(new Location(LOC_TYPE_HOME, colour, (source.loc + i) )))[0] != -1) return locations;
			}
			locations.add(new Location(LOC_TYPE_HOME, colour, source.loc+card));
		}
		
		//----- Check normal spots
		else if (source.type == LOC_TYPE_NORMAL) {
			int dest = (source.loc + card) % 72;
			
			//--- Make sure it's positive
			dest = (dest < 0) ? dest + 72 : dest;
			
			//--- Check if we can go home
			if ((dest >= (colour*18)) && (dest <= ((colour*18)+3)) && (card != -4)) {
				boolean can_go_home = true;
				
				//--- Check if we can go home but not pass
				//     Only time this happens is with a joker
				if (card == 25) {
					//-- Find the spot of previous no-pass
					int last_colour = colour - 1;
					last_colour = (last_colour < 0) ? 3 : last_colour;
					int last_spot = COME_OUT_SPOTS[last_colour];
					
					//-- Check if a piece is there
					if (theBoard.rightPiece(last_colour, new Location(LOC_TYPE_NORMAL, last_spot)))
						can_go_home = false;
				}
				
				//--- Check if the home spots needed are clear
				for (int i=0; i < (dest % 18); i++) {
					if ((theBoard.pieceAt(new Location(LOC_TYPE_HOME, colour, i)))[0] != -1) can_go_home = false;
				}
				
				//--- Check if we're just past the come out spot
				for (int i=0; i<4; i++) {
					if (source.isEqual(new Location(LOC_TYPE_NORMAL, COME_OUT_SPOTS[colour]+i)))
						can_go_home = false;
				}
				
				if (can_go_home) {
					//--- If it's a 7 we want to mark the other possible home moves
					if (card == 7) {
						for (int i=0; i<=(dest%18); i++) {
							if (i+1 <= (sevenCount-(TockBoard.calcDistance(source, new Location(LOC_TYPE_HOME, colour, 0))))) locations.add(new Location(LOC_TYPE_HOME, colour, i));
						}
					} else {
						locations.add(new Location(LOC_TYPE_HOME, colour, (dest % 18)));
					}
				}
			}
			
			//--- Check if can pass we just show the normal destination 
			if (card == 7) {
				//-- Normal moves
				for (int i=1; i<=sevenCount; i++) {
					if (theBoard.canPass(source, i)) {
						locations.add(new Location(LOC_TYPE_NORMAL, (source.loc + i) % 72));
					} else {
						//--- We let the player tock themselves but no further
						locations.add(new Location(LOC_TYPE_NORMAL, (source.loc + i) % 72));
						break;
					}
				}
				
				//-- We'll check to make sure that the 7 is behind the comeout spot
				int tempSrcTarget = (colour==0) ? 72 : (colour*18);
				
				//-- We also make sure that the diff is less than a large number (18 will do) for the case
				//-   when we are on the comeout spot of the bottom left colour.
				if ((source.loc < tempSrcTarget) && (Math.abs(source.loc - tempSrcTarget) < 18)) {
					//-- Moves to home (we arrive here when a full 7 doesn't work going home)
					for (int i=1; i<=sevenCount; i++) {
						int destNum = (source.loc + i) % 72;
						
						//--- Check if we can go home
						if ((destNum >= (colour*18)) && (destNum <= ((colour*18)+3))) {
							//-- Spot in question
							Location loc = (colour == 0) ? new Location(LOC_TYPE_HOME, colour, destNum) :
								new Location(LOC_TYPE_HOME, colour, (destNum % (colour*18)));
							
							//-- Check if it's open
							if ((theBoard.pieceAt(loc))[0] != -1) {
								//- Add it for self tock and break
								locations.add(loc);
								break;
							} else {
								locations.add(loc);
							}
						}
					}
				}
			} else { // Just a normal card
				if (theBoard.canPass(source, card)) {
					locations.add(new Location(LOC_TYPE_NORMAL, colour, dest));
				}
			}
			
			//--- Check for the 10 switches
			if (card == 10) {
				for (int col=0; col<4; col++) {
					for (int num=0; num<4; num++) {
						Location switchTarget = theBoard.getLocation(col, num);
						if ((!switchTarget.isEqual(source)) && (switchTarget.type == LOC_TYPE_NORMAL)) {
							locations.add(switchTarget);
						}
					}
				}
			}
		}
		
		return locations;
	}
	
	/**
	 * Method to get valid moves of a given card.
	 * @param source The source location
	 * @param card The card id (from 1-13, 25)
	 * @param player Name of the player
	 * @return ArrayList of the destination locations
	 */
	public ArrayList<Location> getPossibleMoves(Location source, int card, String player, int sevenCount) {
		return getPossibleMoves(source, card, users.getPlayerIndex(player), sevenCount);
	}
	
	/**
	 * Method to "smartly" determine if we are playing the game or not
	 * @return if we're playing the game
	 */
	public boolean isPlaying() {
		int numPlayers = users.numOfPlayers();
		return (numPlayers == numOfPlayers);
	}
	
}
